package com.agmte.api.app.service;

import com.agmte.api.app.entity.ApiGroup;
import com.agmte.api.app.entity.ApiInfo;
import com.agmte.api.app.entity.pojo.ApiMapper;
import com.agmte.api.app.entity.pojo.ApiParamInfo;
import com.agmte.api.common.crud.core.CrudDb;
import com.agmte.api.common.crud.core.pojo.Page;
import com.agmte.api.common.crud.core.pojo.Pagination;
import com.agmte.api.common.crud.utils.MapperXmlParser;
import com.agmte.api.common.utils.UrlUtil;
import com.agmte.api.config.filter.Knife4ApiScanner;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringStack;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@Service
public class ApiInfoService {
    @Resource
    private Knife4ApiScanner scanner;

    public void save(ApiInfo apiInfo) {
        apiInfo.setPath(UrlUtil.format(apiInfo.getPath()));

        ApiInfo query = new ApiInfo();
        query.setPath(apiInfo.getPath());
        List<ApiInfo> list = CrudDb.list(query);
        if(apiInfo.getId() == null) {
            if (list.size() > 0) {
                throw new RuntimeException(apiInfo.getPath() + "接口已存在");
            }
        }else {
            if (list.size()== 0) {
                throw new RuntimeException(apiInfo.getPath() + "接口不存在");
            }
        }

        for (ApiMapper mapper : apiInfo.getMappers()) {
            this.checkPage(mapper);
        }

        CrudDb.saveOrUpdate(apiInfo);

        scanner.apiScanner();
    }

    public JSONObject getApi(String path, JSONObject body) throws Exception {
        JSONObject result = new JSONObject();


        ApiInfo query = new ApiInfo();
        query.setPath(path);
        List<ApiInfo> apiInfos = CrudDb.list(query);

        if (!apiInfos.isEmpty()) {

            ApiInfo apiInfo = apiInfos.get(0);

            List<ApiMapper> mappers = apiInfo.getMappers();

            for (ApiMapper mapper : mappers) {
                String code = mapper.getCode();

                JSONObject jsonObject = body.getJSONObject(code);

                Object res = queryByApiMapper(mapper, jsonObject);
                result.put(code, res);
            }
        }

        return result;
    }

    public Object testMapper(ApiMapper mapper) throws Exception {
        this.checkPage(mapper);
        JSONObject body = this.generateParameterByMapper(mapper);
        return queryByApiMapper(mapper, body);
    }

    public Object queryByApiMapper(ApiMapper mapper, JSONObject json) throws Exception {
        json = Objects.isNull(json) ? new JSONObject() : json;

        String code = mapper.getCode();
        String mapperSql = mapper.getMapperSql();


        Map<String, Object> param = new HashMap<>();

        for (ApiParamInfo item : mapper.getParamInfos()) {
            Object o = this.getByJSONObject(json, item.getCode());
            if (item.isRequired() && Objects.isNull(o)) {
                throw new RuntimeException(String.format("参数%s(%s.%s)必须值", item.getName(), code, item.getCode()));
            }
            if (o != null) {
                param.put(item.getCode(), o);
            }
        }
        String sql = MapperXmlParser.parseBySelect(mapperSql, param);



        if (mapper.getResponseType() == 1) {
            List<Map<String, Object>> maps = CrudDb.queryBySql(sql);
            if (!maps.isEmpty()) {
                return maps.get(0);
            } else {
                return new HashMap<String, Object>();
            }
        } else if (mapper.getResponseType() == 2) {
            Integer pageSize = json.getInteger("pageSize");
            if(pageSize == null){
                throw new RuntimeException("未指定 pageSize");
            }
            Integer pageNum = json.getInteger("pageNum");
            if(pageNum == null){
                throw new RuntimeException("未指定 pageNum");
            }
            Page<Map<String,Object>> page = new Page(Map.class,pageNum,pageSize);
            return CrudDb.queryPageBySql(page,sql);
        } else {
            List<Map<String, Object>> maps = CrudDb.queryBySql(sql);
            return maps;
        }
    }

    public JSONObject generateParameterByApi(Long id){
        ApiInfo apiInfo =  CrudDb.byId(ApiInfo.class,id);
        JSONObject params = new JSONObject();
        for (ApiMapper mapper : apiInfo.getMappers()) {
            params.put(mapper.getCode(),generateParameterByMapper(mapper));
        }
        return params;
    }


    public JSONObject generateParameterByMapper(ApiMapper mapper) {
        JSONObject body = new JSONObject();
        for (ApiParamInfo item : mapper.getParamInfos()) {
            if(item.getDatatype().name().contains("array")) {
                if(item.getExample() instanceof String) {
                    item.setExample(((String) item.getExample()).split(","));
                }
            }
            body.put(item.getCode(),item.getExample());
        }
        return body;
    }

    private <T> T getByJSONObject(JSONObject json, String... keys) {
        JSONObject temp = json;
        try {
            if (keys.length == 1) {
                return (T) temp.get(keys[0]);
            }
            for (int i = 0; i < keys.length - 1; i++) {
                temp = temp.getJSONObject(keys[i]);
            }
            Object o = temp.get(keys[keys.length - 1]);

            return (T) o;
        } catch (Exception e) {
            e.printStackTrace();
            log.error(String.join(".", keys) + "不存在对于json");
        }
        return null;
    }


    public Page<ApiInfo> apiList(Pagination pagination, String[] groupIds, String name) {


        String sql = "SELECT * FROM api_info \n" +
                "    <where>\n" +
                "        <if test=\"groupIds!=null and groupIds.length>0\">\n" +
                "            gid in (${groupIds})\n" +
                "        </if>\n" +
                "        <if test=\"name!=null and name.length>0\">\n" +
                "            name like CONCAT('%', #{name}, '%')\n" +
                "        </if>\n" +
                "    </where>";
        Map<String, Object> params = new HashMap<>();
        if(groupIds != null) {
            String collect = Arrays.stream(groupIds).collect(Collectors.joining(","));
            params.put("groupIds", collect );
        }
        params.put("name", name );
        Page<ApiInfo> apiInfoPage = new Page<>(ApiInfo.class, pagination.getPageNum(), pagination.getPageSize());
        CrudDb.page(apiInfoPage,sql,params);

        setGroupName(apiInfoPage.getList());
        return apiInfoPage;
    }

    private void checkPage(ApiMapper mapper){
        if (mapper.getResponseType() == 2) {
            boolean pageSizeB = mapper.getParamInfos().stream().anyMatch(p -> "pageSize".equals(p.getCode()));
            boolean pageNumB = mapper.getParamInfos().stream().anyMatch(p -> "pageNum".equals(p.getCode()));
            if(!pageSizeB){
                throw new RuntimeException(mapper.getName()+ " 未设置 pageSize");
            }
            if(!pageNumB){
                throw new RuntimeException(mapper.getName()+" 未设置 pageNum");
            }
        }
    }

    @Transactional
    public List<String> batchImport(@Valid List<ApiInfo> apiInfos) {
        List<String> fails = new ArrayList<>();
        List<Object[]> glist = CrudDb.list(ApiGroup.class).stream()
                .map(apiGroup -> new Object[]{apiGroup.getId(), apiGroup.getName()}).collect(Collectors.toList());

        for (ApiInfo apiInfo : apiInfos) {
            apiInfo.setId(null);
            Long gid = apiInfo.getGid();
            // 判断分组是否存在，不存在则创建分组
            if(!glist.stream().anyMatch(g ->Objects.equals(g[0], apiInfo.getGid()))){
                if(Strings.isBlank(apiInfo.getGidName())){
                    gid = 0L;
                }else {
                    List<Object[]> gidNames = glist.stream().filter(g -> Objects.equals(g[1], apiInfo.getGidName())).collect(Collectors.toList());
                    if(gidNames.isEmpty()){
                        ApiGroup apiGroupNew = new ApiGroup();
                        apiGroupNew.setName(apiInfo.getGidName());
                        CrudDb.saveOrUpdate(apiGroupNew);
                        gid = apiGroupNew.getId();
                        glist.add(new Object[]{apiGroupNew.getId(), apiGroupNew.getName()});
                    }else {
                        gid =(Long) gidNames.get(0)[0];
                    }
                }
            }
            apiInfo.setGid(gid);
            try {
                this.save(apiInfo);
            }catch (Exception e){
                fails.add(apiInfo.getName()+" 接口导入失败: "+ e.getMessage());
            }

        }
        return fails;

    }


    public void setGroupName(List<ApiInfo> apiInfos) {
        List<ApiGroup> apiGroups = CrudDb.list(ApiGroup.class);
        Map<Long, String> apiGroupsGro = apiGroups.stream().collect(Collectors.toMap(ApiGroup::getId, ApiGroup::getName));
        apiGroupsGro.put(0L, "未定义分组");

        for (ApiInfo apiInfo : apiInfos) {
            if(apiInfo.getGid()!=null) {
                apiInfo.setGidName(apiGroupsGro.get(apiInfo.getGid()));
            }
        }
    }

    public void allExport(HttpServletResponse response) throws IOException {
        List<ApiInfo> apiInfos = CrudDb.list(ApiInfo.class);
        setGroupName(apiInfos);
        // 设置响应头，告诉浏览器这是一个zip文件
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment; filename=apis.zip");

        // 获取响应输出流
        try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
            int index = 1;
            for (ApiInfo apiInfo : apiInfos) {
                // 将对象转为json字符串

                String json = JSONObject.toJSONString(apiInfo);

                // 创建zip中的entry，命名为 apiinfo_1.json, apiinfo_2.json ...
                ZipEntry entry = new ZipEntry(apiInfo.getName() + ".json");
                zipOut.putNextEntry(entry);

                // 写入json字符串，注意转成字节流
                byte[] bytes = json.getBytes("UTF-8");
                zipOut.write(bytes);

                zipOut.closeEntry();
                index++;
            }
            zipOut.finish();
        } catch (IOException e) {
            // 处理异常，日志或返回错误信息
            e.printStackTrace();
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "导出失败");
        }
    }
}
